﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Xml;

using SlimDX;
using Vxl.Utils;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using System.Collections;


namespace Vxl.Drawables
{
    [DataContract(Name = "Vxl")]
    public struct VxlPoint
    {
        public VxlPoint(Vector3 position, Vector4 color)
        {
            this.color = color;
            this.position = position;
        }

        [DataMember(Name = "Position")]
        public Vector3 position;
        [DataMember(Name = "Color")]
        public Vector4 color;

        public static int Size = 3 * 4 + 4 * 4;
    };

    [DataContract(Name = "VxlObject")]
    [KnownType(typeof(Drawable))]
    [KnownType(typeof(VxlPoint))]
    public class VxlObject : Drawable
    {
        [DataMember(Name = "Vxls")]
        protected ArrayList  Vxls;

        [DataMember(Name = "Vxl Size")]
        protected float VxlSize;
        
        private bool isInitialized = false;
        private static bool effectBeenSet = false;
        private static CustomEffect vxlEffect = null;

        public VxlObject()
        {
            Vxls = new ArrayList();
            VxlSize = 0.5f;
            isInitialized = false;
            world = Matrix.Identity;
            Translation = Matrix.Identity;
            Position = Vector3.Zero;
            Rotation = Matrix.Identity;

            this.A = 0.600672f;
            this.B = 0.668533f;
        }

        public void CleanUp()
        {
            if (vxlEffect != null)
            {
                vxlEffect.CleanUp();
                effectBeenSet = false;
            }

            vertexBuffer.Dispose();
            Vxls.Clear();
        }

        public void ResetVertexBuffer(){
            if(!isInitialized){
                this.Initialize();
                return;
            }

            vertexBuffer.Dispose();

            var stream = new DataStream(Vxls.Count * VxlPoint.Size, true, true);

            stream.WriteRange(Vxls.Cast<VxlPoint>().ToArray<VxlPoint>());

            stream.Position = 0;

            vertexBuffer = new SlimDX.Direct3D11.Buffer(Globals.Device, stream, new BufferDescription()
            {
                BindFlags = BindFlags.VertexBuffer,
                CpuAccessFlags = CpuAccessFlags.None,
                OptionFlags = ResourceOptionFlags.None,
                SizeInBytes = Vxls.Count * VxlPoint.Size,
                Usage = ResourceUsage.Default
            });
            stream.Dispose();

            vertexBufferBinding = new VertexBufferBinding(vertexBuffer, VxlPoint.Size, 0);
            isInitialized = true;
        }

        public void AddVxl(Vector3 posInObject, Vector4 color)
        {
            Vxls.Add(new VxlPoint(posInObject * VxlSize, color));
        }

        public void InitializeFromLoad(){

            isInitialized = false;
            world = Matrix.Identity;
            Translation = Matrix.Identity;
            Position = Vector3.Zero;
            Rotation = Matrix.Identity;

            SetMaterialInfo(this.A, this.B);
            Initialize();
        }

        public void Initialize()
        {
            if (!effectBeenSet)
            {
                List<InputElement> layout = new List<InputElement>();
                layout.AddRange(new []{
                    new InputElement("POSITION", 0, Format.R32G32B32_Float, 0, 0),
                    new InputElement("TEXCOORD", 0, Format.R32G32B32A32_Float, 12, 0) 
                });

			    vxlEffect = new CustomEffect("Vxl.fxo", Vxl.Properties.Resources.Vxl, "fx_5_0", "VxlTechnique", layout);
				
			    vxlEffect.AddVariable("Projection");
			    vxlEffect.AddVariable("View");
			    vxlEffect.AddVariable("World");
			    vxlEffect.AddVariable("cubeSize");
			    vxlEffect.AddVariable("CameraPos");
			    vxlEffect.AddVariable("LightDirection");
			    vxlEffect.AddVariable("A");
			    vxlEffect.AddVariable("B");
			    vxlEffect.AddVariable("rhoOverPi");
			    vxlEffect.AddVariable("LightColor");
                vxlEffect.AddVariable("AmbientColor");
                
                effectBeenSet = true;
            }

            if (Vxls.Count == 0)
                return;

            var stream = new DataStream(Vxls.Count * VxlPoint.Size, true, true);

            stream.WriteRange(Vxls.Cast<VxlPoint>().ToArray<VxlPoint>());

            stream.Position = 0;

            vertexBuffer = new SlimDX.Direct3D11.Buffer(Globals.Device, stream, new BufferDescription()
            {
                BindFlags = BindFlags.VertexBuffer,
                CpuAccessFlags = CpuAccessFlags.None,
                OptionFlags = ResourceOptionFlags.None,
                SizeInBytes = Vxls.Count * VxlPoint.Size,
                Usage = ResourceUsage.Default
            });
            stream.Dispose();

            vertexBufferBinding = new VertexBufferBinding(vertexBuffer, VxlPoint.Size, 0);
            isInitialized = true;
        }       
 
        public static void SetPerFrame()
	    {
		    vxlEffect.SetMatrix("Projection",Globals.ActiveCamera.Projection);
		    vxlEffect.SetMatrix("View", Globals.ActiveCamera.View);

            vxlEffect.SetVector("CameraPos", Vector3.Normalize(Vector3.Negate(Globals.ActiveCamera.Position)));
		    vxlEffect.SetVector("LightDirection", Vector3.Normalize(Vector3.Negate(new Vector3(100, 100, 100))));
		    vxlEffect.SetVector("LightColor", new Vector3(1.0f, 1.0f, 1.0f));	
		    vxlEffect.SetVector("AmbientColor", new Vector3(0.3f , 0.3f, 0.3f));
         
		    Globals.Device.ImmediateContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.PointList;
            
		    vxlEffect.PreDraw();
	    }

	    public void SetPerObjectShaderParameters(){
		    vxlEffect.SetFloat("A", A);
            vxlEffect.SetFloat("B", B);
		    vxlEffect.SetFloat("rhoOverPi", rhoOverPi);

		    vxlEffect.SetMatrix("World", world);
		    vxlEffect.SetFloat("cubeSize", VxlSize);
	    }

	    public void Draw(){

            if (Vxls.Count == 0)
                return;

            if (!isInitialized)
                throw new Exception("VxlObject is not initialized");

		    SetPerObjectShaderParameters();
		    
            Globals.Device.ImmediateContext.InputAssembler.SetVertexBuffers(0, vertexBufferBinding);

		    vxlEffect.Draw(Vxls.Count);
	    }

	    public void Update(float dt){
            if (worldMatrixHasChanged)
            {
                world = Rotation * Translation;
                worldMatrixHasChanged = false;
            }
	    }
    }
}
